﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Web.UI.WebControls;
using System.Web.UI;
using System.Web.UI.HtmlControls;

using AjaxControlToolkit;
using BizElements.Core;
using BizElements.BusinessLayer;

namespace BizElements.Web
{
    /// <summary>Base class for user control that display and edit business object which implement <see cref="BizElements.BusinessLayer.IBusinessObject"/> interface.</summary>
    public abstract class BusinessObjectUserControlBase<T> : UserControl, IBusinessObjectUI<T>
        where T : class, IBusinessObject
    {
        #region CTor.

        /// <summary>Initializes a new instance of BusinessObjectUserControlBase class.</summary>
        public BusinessObjectUserControlBase()
        {
            this.Bindings = new EntityBindingContext();
        }

        #endregion

        #region Standard sub-controls: decorative elements, buttons, containers, tab...

        /// <summary>Form header or label control that contains edited object name, ie. default property value. Hidden if <see cref="HeaderAndFooterHidden"/> property is set to <b>true</b>.</summary>
        protected ITextControl HeaderControl { get; set; }

        /// <summary>Array of controls which are to be hidden if <see cref="HeaderAndFooterHidden"/> property is set to <b>true</b>.</summary>
        protected Control[] HeaderAndFooterDecorativeControls { get; set; }

        /// <summary>A control, often a TD or DIV element, that contains all controls bound to fields.</summary>
        protected HtmlControl FieldControlsContainer { get; set; }

        /// <summary>Control, often a TR or DIV element, that contains save, deactivate, cancel and other custom command buttons. Hidden if <see cref="ButtonsHidden"/> property is set to <b>true</b>.</summary>
        protected Control CommandButtonsContainer { get; set; }

        /// <summary>Save button.</summary>
        protected TemplateButton SaveButton { get; set; }

        /// <summary>Deactivate/delete button.</summary>
        protected TemplateButton DeactivateButton { get; set; }

        /// <summary>Cancel/close form button.</summary>
        protected TemplateButton CancelButton { get; set; }

        /// <summary>Tab control that contains child collection editors.</summary>
        protected TabContainer ChildrenTabContainer { get; set; }

        #endregion

        #region Bindings.

        /// <summary>Field, parent and children bindings.</summary>
        protected IEntityBindingContext Bindings { get; private set; }

        /// <summary>Initializes field, parent and children bindings.</summary>
        protected abstract void InitBindings();

        /// <summary>Gets adapter for specified complex property.</summary>
        protected IComplexPropertyAdapter GetComplexPropertyAdapter(string propertyName)
        {
            return ArrayUtil.Find<IComplexPropertyAdapter>(this.BizObject.GetDescriptor().ComplexProperties, (adapt) => (adapt.PropertyName == propertyName));
        }

        #endregion

        #region Init, Load, PreRender.

        /// <summary>Initializes field bindings but waits till <b>InitComplete</b> event the parent page occurs to setup child collection editors, ie. call <see cref="InitAccessModesForCollectionEditors"/> method.</summary>
        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);
            InitBindings();
            // Child user controls are not instantiated in the momennt when ASPX and ASCX files/templates are being processed so their ReadOnly properties are set after Init phase.
            // Additionaly, read-only mode may be changed during the page's Init phase. This way we ensure that these changes are honored.
            this.Page.InitComplete += delegate(object o, EventArgs ea) { InitAccessModesForCollectionEditors(); };
        }

        /// <summary>Initializes access modes for collection editors. Invoked when Page.InitComplete event is fired.</summary>
        protected virtual void InitAccessModesForCollectionEditors()
        {
            if (this.ReadOnly)
            {
                foreach (IChildrenBinding childrenBinding in this.Bindings.ChildrenBindings)
                    MakeReadOnlyChildren(childrenBinding.GetCollectionEditor(this));
            }
        }

        /// <summary>Displays data and captions, registers search providers, checks security permissions, enables/disables controls.</summary>
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            // Null can only occur during design time. In runtime, owner pages always initialize this property.
            if (this.BizObject == null)
                return;

            SetupChildrenCollectionControls();
            if (!IsPostBack)
            {
                IActor user = GetActor();
                CheckFunctions(user);
                FillAllListControls();

                DisplayHeader(user);
                if (!this.ChildrenHidden && this.Bindings.ChildrenBindings.Count > 0)
                    DisplayChildren(this.Bindings.ChildrenBindings[0].ChildrenPropertyName);
            }
            else
            {
                // Esnure that read-only flag is honored after every post-back.
                if (this.ReadOnly)
                    this.Bindings.FieldBindings.PropagateAccessMode(FieldAccessMode.ReadOnly);
            }

            RemoveRequiredFieldIndicatorsIfInReadOnlyMode();
            if (this.HeaderAndFooterHidden)
                HideHeaderAndFooter();

            RegisterAllParentSearchProviders();
            SetNewAndDetailsParentCaptions();
            SetButtonCaptions();
            SetComplexPropertyControlsVisibility();
        }

        /// <summary>Hides decorative elements, such as header and footer. Typically used when the user control is embeded in another user control.</summary>
        protected virtual void HideHeaderAndFooter()
        {
            if (this.HeaderAndFooterDecorativeControls != null)
            {
                foreach (Control ctl in this.HeaderAndFooterDecorativeControls)
                    ctl.Visible = false;
            }

            this.FieldControlsContainer.Style["border-style"] = "none";
        }

        private void SetComplexPropertyControlsVisibility()
        {
            foreach (IComplexPropertyBinding complexProp in this.Bindings.ComplexPropertyBindings)
            {
                var adapter = GetComplexPropertyAdapter(complexProp.PropertyName);
                var childEntity = adapter.GetChild(this.BizObject);
                bool isVisible = (childEntity != null);

                var uc = complexProp.GetUserControl(this);
                uc.ButtonsHidden = true;
                uc.Visible = isVisible;
                foreach (string decorationId in complexProp.DecorativeControlIds)
                {
                    var ctl = WebUtil.RecursivelyFindControl(this, decorationId);
                    if (ctl != null)
                        ctl.Visible = isVisible;
                }

            }
        }

        /// <summary>Hides Save and Deactivate buttons, ie. calls <see cref="HideButtonsUnavailableInReadOnlyMode"/> method, if the object is in read-only mode and checks additional field access permissions by calling <see cref="CheckFieldAccessPermissions"/> method.</summary>
        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);
            if (this.BizObject == null)
                return;

            if (this.BizObject.IsReadOnly)
                HideButtonsUnavailableInReadOnlyMode();

            CheckFieldAccessPermissions(GetActor());
        }

        /// <summary>Hides Save and Deactivate buttons. Override to add custom buttons which are to be hidded in read-only mode. Called in PreRender phase if the object is in read-only mode.</summary>
        protected virtual void HideButtonsUnavailableInReadOnlyMode()
        {
            this.SaveButton.Visible = this.DeactivateButton.Visible = false;
        }

        #endregion

        #region CheckFunctions.

        /// <summary>Implements vertical (action privileges) and horizontal (record-base) security. Disables functions which the user is not authorized to use.
        /// Override to add security checks for custom actions and buttons.</summary>
        protected virtual void CheckFunctions(IActor user)
        {
            // Bound controls automatically switch read-only mode if object's IsReadOnly property is set to true.
            if (this.ReadOnly)
                this.BizObject.IsReadOnly = true;

            if (this.ButtonsHidden)
            {
                // If all buttons are hidden then it's not necessary to check priviliges. This may enhance performance. Typically used when the UC is embeded in another UC.
                if (this.CommandButtonsContainer != null)
                    this.CommandButtonsContainer.Visible = false;

                this.SaveButton.Visible = this.DeactivateButton.Visible = this.CancelButton.Visible = false;
            }
            else
            {
                // Check save and deactivate command privileges.
                WebUtil.CheckControlRules(this.BizObject.GrantSave(user), this.SaveButton);
                var meta = this.BizObject.GetDescriptor();
                if (!this.BizObject.IsNew && !user.HasPermission(meta.Permissions.UpdatePermission))
                {
                    this.BizObject.IsReadOnly = true;
                    this.DeactivateButton.Visible = this.SaveButton.Visible = false;
                }
                else
                {
                    this.DeactivateButton.Visible = !this.BizObject.IsNew;
                    if (this.DeactivateButton.Visible)
                        WebUtil.CheckControlRules(this.BizObject.GrantDeactivate(user), this.DeactivateButton);
                }
            }

            // Check privileges to create and edit parent entities. Possible only if default concrete BL class exists for parent table.
            foreach (IParentBinding parentBinding in this.Bindings.ParentBindings)
            {
                var parentMeta = parentBinding.TryGetBusinessObjectDescriptor();
                Control createNewParentControl = parentBinding.GetCreateNewParentControl(this);
                if (createNewParentControl != null)
                {
                    if (parentMeta != null)
                        createNewParentControl.Visible = !this.ReadOnly && user.HasPermission(parentMeta.Permissions.CreatePermission);
                    else
                        createNewParentControl.Visible = false;
                }

                Control openParentDetailsControl = parentBinding.GetOpenParentDetailsControl(this);
                if (openParentDetailsControl != null)
                {
                    if (parentMeta != null)
                        openParentDetailsControl.Visible = user.HasPermission(parentMeta.Permissions.UserInterfacePermission) && user.HasPermission(parentMeta.Permissions.ReadPermission);
                    else
                        openParentDetailsControl.Visible = false;
                }
            }
        }

        /// <summary>Checks field access permissions. Honors rules set in object's <see cref="IBusinessObjectDescriptor"/>. Called in PreRender stage.</summary>
        protected virtual void CheckFieldAccessPermissions(IActor user)
        {
            var meta = this.BizObject.GetDescriptor();
            foreach (FieldPermissionSet fieldPermissions in meta.FieldPermissions)
            {
                Control ctl = GetControlBoundToField(fieldPermissions.PropertyName) as Control;
                if (ctl != null)
                {
                    if (this.BizObject.IsNew && fieldPermissions.CreatePermission != null)
                        ctl.Visible = false;

                    if (!this.BizObject.IsNew && fieldPermissions.UpdatePermission != null)
                    {
                        if (ctl is IBoundControl)
                            (ctl as IBoundControl).AccessMode = FieldAccessMode.ReadOnly;
                        else if (ctl is WebControl)
                            (ctl as WebControl).Enabled = false;
                    }

                    if (!this.BizObject.IsNew && fieldPermissions.ReadPermission != null)
                        ctl.Visible = false;
                }
            }
        }

        #endregion

        #region Public interface.

        T bizObject;

        /// <summary>Gets or sets the object displayed/edited by current control. Sets complex properties to embeded controls.</summary>
        public virtual T BizObject
        {
            get { return this.bizObject; }
            set
            {
                this.bizObject = value;
                foreach (IComplexPropertyBinding complexProp in this.Bindings.ComplexPropertyBindings)
                {
                    var uc = complexProp.GetUserControl(this);
                    if (value != null)
                    {
                        var adapter = GetComplexPropertyAdapter(complexProp.PropertyName);
                        uc.BizObject = adapter.GetChild(value);
                    }
                    else
                    {
                        uc.BizObject = null;
                    }
                }
            }
        }

        IDataObject IDataObjectUI.BizObject
        {
            get { return this.BizObject; }
            set
            {
                if (value != null)
                    this.BizObject = (T)value;
                else
                    this.bizObject = null;
            }
        }

        /// <summary>Indicates whether the component is in read-only mode. The property may be set in design time (ASPX/ASCX file) or during the Init stage of the owning page. Has no effect after Init phase.</summary>
        public bool ReadOnly { get; set; }

        /// <summary>Specifies whether the button/command container (typically TR or DIV element) is visible. Typically set to <b>false</b> when the control is embeded within another user control.</summary>
        public bool ButtonsHidden { get; set; }

        /// <summary>Specifies whether the decorative elements (header and footer) are visible. Typically set to <b>false</b> when the control is embeded within another user control.</summary>
        public bool HeaderAndFooterHidden { get; set; }

        /// <summary>Specifies whether the child collection editors are visible.</summary>
        public bool ChildrenHidden { get; set; }

        /// <summary>Occurs when the user save the object.</summary>
        public event EventHandler Saved;

        /// <summary>Occurs when the user discards changes.</summary>
        public event EventHandler Canceled;

        /// <summary>Occurs when the object is deactivated/deleted.</summary>
        public event EventHandler Deactivated;

        /// <summary>Occurs when the user requests to open a dedicated form to edit an existing child entity.</summary>
        public event EventHandler<EventArgs<WebFormContext>> OpenChild;

        /// <summary>Occurs when the user requests to open a dedicated form to create a new child entity.</summary>
        public event EventHandler<EventArgs<WebFormContext>> NewChild;

        /// <summary>Occurs when the user requests to open a dedicated form to edit an existing parent entity.</summary>
        public event EventHandler<EventArgs<WebFormContext>> OpenParent;

        /// <summary>Occurs when the user requests to open a dedicated form to create a new parent entity.</summary>
        public event EventHandler<EventArgs<WebFormContext>> NewParent;

        /// <summary>Refreshes/redraws the user control and all embedded components if they exist.</summary>
        public virtual void Refresh()
        {
            IActor user = GetActor();
            CheckFunctions(user);
            DisplayHeader(user);
            foreach (IChildrenBinding childrenBinding in this.Bindings.ChildrenBindings)
                childrenBinding.GetCollectionEditor(this).Refresh();
        }

        /// <summary>Focuses the control bound to default object field.</summary>
        public override void Focus()
        {
            var meta = this.BizObject.GetDescriptor();
            if (!string.IsNullOrEmpty(meta.Fields.DefaultPropertyName))
            {
                Control boundcontrol = GetControlBoundToField(meta.Fields.DefaultPropertyName) as Control;
                if (boundcontrol != null)
                {
                    if (string.IsNullOrEmpty(boundcontrol.ClientID))
                        JScript.Focus(boundcontrol);
                    else
                        JScript.Focus(boundcontrol.ClientID, 200);
                }
            }
        }

        /// <summary>Checks if there are unsubmitted child items.</summary>
        public virtual bool HasUnsubmittedChanges()
        {
            foreach (IChildrenBinding childrenBinding in this.Bindings.ChildrenBindings)
            {
                if (childrenBinding.GetCollectionEditor(this).HasUnsubmittedChanges())
                    return true;
            }

            return false;
        }

        /// <summary>Forces all collection editors to reject changed or new items that haven't been submitted/accepted.</summary>
        public virtual void RejectUnsubmittedChanges()
        {
            foreach (IChildrenBinding childrenBinding in this.Bindings.ChildrenBindings)
                childrenBinding.GetCollectionEditor(this).RejectUnsubmittedChanges();
        }

        /// <summary>Checks if any of the collection editors has errors.</summary>
        public virtual bool HasErrors()
        {
            foreach (IChildrenBinding childrenBinding in this.Bindings.ChildrenBindings)
            {
                if (childrenBinding.GetCollectionEditor(this).HasErrors())
                    return true;
            }

            return false;
        }

        /// <summary>Parses and validates business object and displays found errors.</summary>
        public virtual bool PopulateAndValidateBusinessObject()
        {
            RejectUnsubmittedChanges();
            if (HasErrors())
            {
                // Can't continue if there are errors in child collections.
                JScript.Alert(UiMessages.CorrectDisplayedErrors);
                return false;
            }

            // Complex properties (ISelfValidating objects) will be validated again in Validate() method of master business object.
            bool allComplexPropertiesValid = PopulateAndValidateComplexProperties();
            bool isValid = BindingsUtil.PopulateAndValidateBusinessObject(this.BizObject, this.Bindings.FieldBindings);
            return isValid && allComplexPropertiesValid;
        }

        /// <summary>Parses visible complex properties and executes a basic validation implemented in ISelfValidating objects.</summary>
        protected virtual bool PopulateAndValidateComplexProperties()
        {
            bool allValid = true;
            foreach (IComplexPropertyBinding complexProp in this.Bindings.ComplexPropertyBindings)
            {
                var uc = complexProp.GetUserControl(this);
                if (uc.Visible)
                {
                    var adapter = GetComplexPropertyAdapter(complexProp.PropertyName);
                    var childEntity = adapter.GetChild(this.BizObject);
                    if (childEntity != null)
                    {
                        bool isCurrEntityValid = uc.PopulateAndValidateBusinessObject();
                        allValid &= isCurrEntityValid;
                    }
                }
            }

            return allValid;
        }

        /// <summary>Changes business object access mode and propagates it to all bound controls.</summary>
        /// <param name="accessMode">Access mode.</param>
        /// <param name="doRefresh">Indicates whether the control should automatically refresh itself. Set to <b>false</b> if you will call <see cref="Refresh"/> method manually.</param>
        public virtual void ForceAccessMode(FieldAccessMode accessMode, bool doRefresh)
        {
            this.Bindings.FieldBindings.PropagateAccessMode(accessMode);
            if (accessMode == FieldAccessMode.ReadOnly)
                this.ReadOnly = this.BizObject.IsReadOnly = true;
            else if (accessMode == FieldAccessMode.Edit)
                this.ReadOnly = this.BizObject.IsReadOnly = false;

            if (doRefresh)
                Refresh();
        }

        #endregion

        #region Header/root entity fields.

        /// <summary>Displays the header fields, ie. fields that belong to the master object.</summary>
        protected virtual void DisplayHeader(IActor user)
        {
            this.Bindings.FieldBindings.DisplayBusinessObject(this.BizObject);
            SetHeaderCaption(user);
        }

        /// <summary>Sets header caption.</summary>
        protected virtual void SetHeaderCaption(IActor user)
        {
            // If caption is not set then try to get default object name. Business layer resources should contain an entry with resource key equal to table name.
            if (string.IsNullOrEmpty(this.HeaderControl.Text))
                this.HeaderControl.Text = this.BizObject.GetFieldCaption("");

            if (!this.BizObject.IsNew)
            {
                var meta = this.BizObject.GetDescriptor();
                if (!string.IsNullOrEmpty(meta.Fields.DefaultPropertyName))
                {
                    string objectName = " - " + ReflectionUtil.GetPublicPropertyValue(this.BizObject, meta.Fields.DefaultPropertyName);
                    if ((meta.Permissions.ViewPkPermission != null) && user.HasPermission(meta.Permissions.ViewPkPermission) && (this.BizObject.Table.PrimaryKey.Count > 0))
                        objectName += string.Format(" (ID: {0})", ArrayUtil.Concat(this.BizObject.GetPrimaryKeyValue(), ";"));
                    this.HeaderControl.Text = TextUtil.EnsureSuffix(this.HeaderControl.Text, objectName);
                }
            }
        }

        /// <summary>Asterisks are not displayed in read-only mode.</summary>
        protected virtual void RemoveRequiredFieldIndicatorsIfInReadOnlyMode()
        {
            bool shouldRemoveRequiredFieldIndicators = this.ReadOnly;
            if (!shouldRemoveRequiredFieldIndicators)
                return;

            foreach (IFieldBinding fieldBinding in this.Bindings.FieldBindings.FieldBindings)
            {
                ITextControl label = fieldBinding.CaptionLabel as ITextControl;
                if (label != null)
                    label.Text = TextUtil.RemoveSuffix(label.Text, BindingsUtil.RequiredFieldIndicator);
            }
        }

        /// <summary>Binds property to specified edit/display and caption controls.</summary>
        /// <param name="dbField">Database field.</param>
        /// <param name="captions">An object that returns field captions/label texts.</param>
        /// <param name="fieldControl">Edit/display control.</param>
        /// <param name="captionLabel">Caption control.</param>
        /// <param name="satelliteFields">Optional satellite fields.</param>
        /// <returns>Bound field.</returns>
        protected IBindableField BindField(IDbColumn dbField, ICaptionProvider captions, IBoundControl fieldControl, ITextControl captionLabel, params string[] satelliteFields)
        {
            return BindingsUtil.BindField(this.Bindings.FieldBindings, dbField, captions, fieldControl, captionLabel, satelliteFields);
        }

        /// <summary>
        /// Binds property to specified edit/display and caption controls.
        /// </summary>
        /// <param name="dbField">Database field.</param>
        /// <param name="captions">An object that returns field captions/label texts.</param>
        /// <param name="fieldControl">Edit/display control.</param>
        /// <param name="captionLabel">Caption control.</param>
        /// <param name="satelliteFields">Optional satellite fields.</param>
        /// <returns>Bound field.</returns>
        protected IBindableField BindField(IDbColumn dbField, ICaptionProvider captions, BoundTextBox fieldControl, ITextControl captionLabel, params string[] satelliteFields)
        {
            return BindingsUtil.BindField(this.Bindings.FieldBindings, dbField, captions, fieldControl, captionLabel, satelliteFields);
        }

        /// <summary>
        /// Binds property to specified edit/display and caption controls.
        /// </summary>
        /// <param name="dbField">Database field.</param>
        /// <param name="captions">An object that returns field captions/label texts.</param>
        /// <param name="isNullable">Whether the value is required or not.</param>
        /// <param name="fieldControl">Edit/display control.</param>
        /// <param name="captionLabel">Caption control.</param>
        /// <param name="satelliteFields">Optional satellite fields.</param>
        /// <returns>Bound field.</returns>
        protected IBindableField BindField(IDbColumn dbField, bool isNullable, ICaptionProvider captions, IBoundControl fieldControl, ITextControl captionLabel, params string[] satelliteFields)
        {
            return BindingsUtil.BindField(this.Bindings.FieldBindings, dbField, isNullable, captions, fieldControl, captionLabel, satelliteFields);
        }

        /// <summary>
        /// Binds property to specified edit/display and caption controls.
        /// </summary>
        /// <param name="dbField">Database field.</param>
        /// <param name="captions">An object that returns field captions/label texts.</param>
        /// <param name="isNullable">Whether the value is required or not.</param>
        /// <param name="fieldControl">Edit/display control.</param>
        /// <param name="captionLabel">Caption control.</param>
        /// <param name="satelliteFields">Optional satellite fields.</param>
        /// <returns>Bound field.</returns>
        protected IBindableField BindField(IDbColumn dbField, bool isNullable, ICaptionProvider captions, BoundTextBox fieldControl, ITextControl captionLabel, params string[] satelliteFields)
        {
            return BindingsUtil.BindField(this.Bindings.FieldBindings, dbField, isNullable, captions, fieldControl, captionLabel, satelliteFields);
        }

        /// <summary>
        /// Binds property to specified edit/display and caption controls.
        /// </summary>
        /// <param name="property">Public property.</param>
        /// <param name="dataType">Type of data contained in the property.</param>
        /// <param name="isNullable">Whether the value is required or not.</param>
        /// <param name="captions">An object that returns field captions/label texts.</param>
        /// <param name="fieldControl">Edit/display control.</param>
        /// <param name="captionLabel">Caption control.</param>
        /// <param name="satelliteFields">Optional satellite fields.</param>
        /// <returns>Bound field.</returns>
        protected IBindableField BindField(string property, Type dataType, bool isNullable, ICaptionProvider captions, IBoundControl fieldControl, ITextControl captionLabel, params string[] satelliteFields)
        {
            return BindingsUtil.BindField(this.Bindings.FieldBindings, property, dataType, isNullable, captions, fieldControl, captionLabel, satelliteFields);
        }

        /// <summary>
        /// Binds property to specified edit/display and caption controls.
        /// </summary>
        /// <param name="property">Public property.</param>
        /// <param name="dataType">Type of data contained in the property.</param>
        /// <param name="isNullable">Whether the value is required or not.</param>
        /// <param name="captions">An object that returns field captions/label texts.</param>
        /// <param name="fieldControl">Edit/display control.</param>
        /// <param name="captionLabel">Caption control.</param>
        /// <param name="satelliteFields">Optional satellite fields.</param>
        /// <returns>Bound field.</returns>
        protected IBindableField BindField(string property, Type dataType, bool isNullable, ICaptionProvider captions, BoundTextBox fieldControl, ITextControl captionLabel, params string[] satelliteFields)
        {
            return BindingsUtil.BindField(this.Bindings.FieldBindings, property, dataType, isNullable, captions, fieldControl, captionLabel, satelliteFields);
        }

        #endregion

        #region Fire events.

        /// <summary>Fires OpenParent event. Typically handled/processed by parent page which acts as an application/redirections controller.</summary>
        protected virtual void FireOpenParent(WebFormContext parentFormContext)
        {
            if (this.OpenParent != null)
                OpenParent(this, new EventArgs<WebFormContext>(parentFormContext));
        }

        /// <summary>Fires NewParent event. Typically handled/processed by parent page which acts as an application/redirections controller.</summary>
        protected virtual void FireNewParent(WebFormContext parentFormContext)
        {
            if (this.NewParent != null)
                NewParent(this, new EventArgs<WebFormContext>(parentFormContext));
        }

        /// <summary>Fires OpenChild event. Typically handled/processed by parent page which acts as an application/redirections controller.</summary>
        protected virtual void FireOpenChild(WebFormContext childFormContext)
        {
            if (this.OpenChild != null)
                OpenChild(this, new EventArgs<WebFormContext>(childFormContext));
        }

        /// <summary>Fires NewChild event. Typically handled/processed by parent page which acts as an application/redirections controller.</summary>
        protected virtual void FireNewChild(WebFormContext childFormContext)
        {
            if (this.NewChild != null)
                NewChild(this, new EventArgs<WebFormContext>(childFormContext));
        }

        #endregion

        #region Save, deactivate, cancel.

        /// <summary>Parses, validate and saves the object. Fires <see cref="Saved"/> event if the action is successful.</summary>
        protected virtual void HandleSaveCommand(object sender, CommandEventArgs e)
        {
            this.BizObject.SetModifiedBy(GetActor());
            bool isValid = PopulateAndValidateBusinessObject();
            if (isValid)
            {
                this.BizObject.Save(GetActor(), true);
                if (this.Saved != null)
                    Saved(this, new EventArgs());
            }
        }

        /// <summary>Deactivates/deletes the object in the data source. Fires <see cref="Deactivated"/> event if the action is successful.</summary>
        protected virtual void HandleDeactivateCommand(object sender, CommandEventArgs e)
        {
            RejectUnsubmittedChanges();
            this.BizObject.SetModifiedBy(GetActor());
            this.BizObject.Deactivate(GetActor(), true);
            if (this.Deactivated != null)
                Deactivated(this, new EventArgs());
        }

        /// <summary>Fires <see cref="Canceled"/> event.</summary>
        protected virtual void HandleCancelCommand(object sender, CommandEventArgs e)
        {
            if (this.Canceled != null)
                Canceled(this, new EventArgs());
        }

        /// <summary>Sets buttons captions if empty.</summary>
        private void SetButtonCaptions()
        {
            if (string.IsNullOrEmpty(this.SaveButton.Text))
                this.SaveButton.Text = UiMessages.SaveText;
            if (string.IsNullOrEmpty(this.SaveButton.ToolTip))
                this.SaveButton.ToolTip = UiMessages.SaveToolTip;

            if (string.IsNullOrEmpty(this.DeactivateButton.Text))
                this.DeactivateButton.Text = UiMessages.DeactivateText;
            if (string.IsNullOrEmpty(this.DeactivateButton.ToolTip))
                this.DeactivateButton.ToolTip = UiMessages.DeactivateToolTip;
            if (string.IsNullOrEmpty(this.DeactivateButton.ConfirmationMessage))
                this.DeactivateButton.ConfirmationMessage = UiMessages.DeactivateConfirmationMessage;

            if (string.IsNullOrEmpty(this.CancelButton.Text))
                this.CancelButton.Text = UiMessages.CancelText;
            if (string.IsNullOrEmpty(this.CancelButton.ToolTip))
                this.CancelButton.ToolTip = UiMessages.CancelToolTip;
        }

        #endregion

        #region Parents.

        /// <summary>Binds codelists to all list controls (drop-down lists, radio buttons lists, listboxes)</summary>
        protected virtual void FillAllListControls()
        {
            foreach (IParentBinding parentBinding in this.Bindings.ParentBindings)
            {
                FillListControl(parentBinding);
            }
        }

        /// <summary>Binds codelist returned by <see cref="IParentBinding.GetCodeList(IActor)"/> method to list control returned by <see cref="IParentBinding.GetListControl(Control)"/> method.</summary>
        protected virtual void FillListControl(IParentBinding parentBinding)
        {
            var listControl = parentBinding.GetListControl(this);
            var dataSource = parentBinding.GetCodeList(GetActor());
            if (listControl != null && dataSource != null)
            {
                listControl.DataSource = dataSource;
                listControl.DataValueField = dataSource.ValueMember;
                listControl.DataTextField = dataSource.DisplayMember;
                listControl.DataBind();
                if (parentBinding.CanClearSelection)
                    listControl.Items.Insert(0, new ListItem("", ""));
            }
        }

        /// <summary>Ensures that all search engines builders are registered in the <see cref="SearchProviderRegistry"/> for all parent entities which are selected via lookups.</summary>
        protected virtual void RegisterAllParentSearchProviders()
        {            
            foreach (IParentBinding parentBinding in this.Bindings.ParentBindings)
            {
                if (!string.IsNullOrEmpty(parentBinding.SeachProviderBuilderControlId))
                    RegisterParentSearch(parentBinding.ForeignKeyPropertyName);
            }
        }

        /// <summary>Ensures that a global (used by all users and threads) search engine build object is registered in the <see cref="SearchProviderRegistry"/> for the specified FK property. Only single field FKs are supported.</summary>
        protected virtual void RegisterParentSearch(string foreignKeyPropertyName)
        {
            var binding = ArrayUtil.Find<IParentBinding>(this.Bindings.ParentBindings, (bind) => (bind.ForeignKeyPropertyName == foreignKeyPropertyName));
            if (binding == null)
                throw new NotImplementedException("No parent binding defined for property " + foreignKeyPropertyName);

            var lookup = binding.GetLookupControl(this);
            if (lookup == null)
                return;

            var builder = binding.NewSearchEngineBuilder();
            var searchProviderBuilder = binding.GetSearchProviderBuilderComponent(this);
            searchProviderBuilder.EngineBuilder = builder;

            var meta = binding.GetDataObjectDescriptor();
            IDbTable parentTable = binding.GetParentTable();

            IDbColumn defaultField = parentTable.Columns.GetByPropertyName(meta.Fields.DefaultPropertyName);
            string defaultFieldColumnName;
            if (defaultField != null)
                defaultFieldColumnName = defaultField.ColumnName;
            else
                // Bind anything.
                defaultFieldColumnName = builder.CreateSearchEngine(GetActor()).DisplayFields[0].BindingName;

            ISearchPageClient popupLookup = lookup as ISearchPageClient;
            if (popupLookup != null)
            {
                searchProviderBuilder.RegisterForClient(UserSession.UiCulture, popupLookup, true,
                    new string[] { parentTable.PrimaryKey[0].ColumnName, defaultFieldColumnName });
            }
            else
            {
                searchProviderBuilder.RegisterForClient(UserSession.UiCulture, lookup, false,
                new string[] { parentTable.PrimaryKey[0].ColumnName, defaultFieldColumnName });
            }
        }

        /// <summary>Sets captions for new parent and parent details controls if empty.</summary>
        private void SetNewAndDetailsParentCaptions()
        {
            foreach (IParentBinding parentBinding in this.Bindings.ParentBindings)
            {
                if (!string.IsNullOrEmpty(parentBinding.CreateNewParentControlId))
                    SetCreateNewParentControlCaptions(parentBinding.CreateNewParentControlId);

                if (!string.IsNullOrEmpty(parentBinding.OpenParentDetailsControlId))
                    SetOpenParentDetailsControlCaptions(parentBinding.OpenParentDetailsControlId);
            }
        }

        /// <summary>Sets caption for new parent control if empty.</summary>
        private void SetCreateNewParentControlCaptions(string createNewParentControlId)
        {
            WebControl newParentCtl = WebUtil.RecursivelyFindControl(this, createNewParentControlId) as WebControl;
            if (newParentCtl != null)
            {
                if (string.IsNullOrEmpty(newParentCtl.ToolTip))
                    newParentCtl.ToolTip = UiMessages.NewParentToolTip;

                IButtonControl newParentButton = newParentCtl as IButtonControl;
                if (newParentButton != null && string.IsNullOrEmpty(newParentButton.Text))
                    newParentButton.Text = UiMessages.NewParentText;

                TemplateButton btnWithConfirmation = newParentCtl as TemplateButton;
                if (btnWithConfirmation != null && string.IsNullOrEmpty(btnWithConfirmation.ConfirmationMessage))
                    btnWithConfirmation.ConfirmationMessage = UiMessages.NewParentConfirmationMessage;
            }
        }

        /// <summary>Sets caption for open parent details control if empty.</summary>
        private void SetOpenParentDetailsControlCaptions(string openParentDetailsControlId)
        {
            WebControl openParentCtl = WebUtil.RecursivelyFindControl(this, openParentDetailsControlId) as WebControl;
            if (openParentCtl != null)
            {
                if (string.IsNullOrEmpty(openParentCtl.ToolTip))
                    openParentCtl.ToolTip = UiMessages.ParentDetailsToolTip;

                IButtonControl openParentButton = openParentCtl as IButtonControl;
                if (openParentCtl != null && string.IsNullOrEmpty(openParentButton.Text))
                    openParentButton.Text = UiMessages.ParentDetailsText;

                TemplateButton btnWithConfirmation = openParentCtl as TemplateButton;
                if (btnWithConfirmation != null && string.IsNullOrEmpty(btnWithConfirmation.ConfirmationMessage))
                    btnWithConfirmation.ConfirmationMessage = UiMessages.ParentDetailsConfirmationMessage;
            }
        }

        /// <summary>Fires <see cref="OpenParent"/> event. URL will be passed as event's <b>CommandArgument</b>.
        /// Owner page that hosts the user control decides how the specified URL is openned.</summary>
        protected virtual void HandleOpenParentDetailsCommand(object button, CommandEventArgs commandArgsWithForeignKeyPropertyName)
        {
            string fkPropertyName = Convert.ToString(commandArgsWithForeignKeyPropertyName.CommandArgument);
            OpenParentDetails(fkPropertyName);
        }

        /// <summary>Fires <see cref="NewParent"/> event. URL will be passed as event's <b>CommandArgument</b>.
        /// Owner page that hosts the user control decides how the specified URL is openned.</summary>
        protected virtual void HandleCreateNewParentCommand(object button, CommandEventArgs commandArgsWithForeignKeyPropertyName)
        {
            string fkPropertyName = Convert.ToString(commandArgsWithForeignKeyPropertyName.CommandArgument);
            CreateNewParent(fkPropertyName);
        }

        private void OpenParentDetails(string foreignKeyPropertyName)
        {
            IParentBinding bindingInfo = ArrayUtil.Find<IParentBinding>(this.Bindings.ParentBindings, (bind) => (bind.ForeignKeyPropertyName == foreignKeyPropertyName));
            if (bindingInfo == null)
                throw new InvalidOperationException("No binding defined for parent object via property " + foreignKeyPropertyName);

            string selectedValue = bindingInfo.GetSelectedValue(this);
            if (string.IsNullOrEmpty(selectedValue))
                return;

            int? numericId = NumberParser.ParseInt(selectedValue);
            if (numericId.HasValue && numericId.Value <= 0)
                return;

            var parentTable = bindingInfo.GetParentTable();
            string parentPkPropertyName = parentTable.PrimaryKey[0].PropertyName;
            string parentDefaultPropertyName = bindingInfo.GetDataObjectDescriptor().Fields.DefaultPropertyName;

            WebFormContext parentForm = new WebFormContext(bindingInfo.ParentFormUrl, selectedValue);
            // Parent.Id --> Child.ParentId
            parentForm.ReturnPropertyMap.Add(parentTable.PrimaryKey[0].PropertyName, bindingInfo.ForeignKeyPropertyName);
            var lookup = bindingInfo.GetLookupControl(this);
            if (lookup != null)
            {
                // Parent.EmployeeID --> LookupControl.SelectedValue
                parentForm.PropertyToControlMap.Add(parentPkPropertyName, lookup.SelectedValueControl.ClientID);
                // Parent.DefaultTextProperty --> LookupControl.Text
                parentForm.PropertyToControlMap.Add(parentDefaultPropertyName, lookup.TextControl.ClientID);
            }

            var ddlOrRadio = bindingInfo.GetListControl(this);
            if (ddlOrRadio != null)
            {
                // Parent.EmployeeID --> BoundControl.SelectedValue
                parentForm.PropertyToControlMap.Add(parentPkPropertyName, ((Control)ddlOrRadio).ClientID);
            }

            FireOpenParent(parentForm);
        }

        private void CreateNewParent(string foreignKeyPropertyName)
        {
            IParentBinding bindingInfo = ArrayUtil.Find<IParentBinding>(this.Bindings.ParentBindings, (bind) => (bind.ForeignKeyPropertyName == foreignKeyPropertyName));
            if (bindingInfo == null)
                throw new InvalidOperationException("No binding defined for parent object via property " + foreignKeyPropertyName);

            var parentTable = bindingInfo.GetParentTable();
            string parentPkPropertyName = parentTable.PrimaryKey[0].PropertyName;
            string parentDefaultPropertyName = bindingInfo.GetDataObjectDescriptor().Fields.DefaultPropertyName;

            WebFormContext parentForm = new WebFormContext(bindingInfo.ParentFormUrl);
            // Parent.Id --> Child.ParentId
            parentForm.ReturnPropertyMap.Add(parentTable.PrimaryKey[0].PropertyName, bindingInfo.ForeignKeyPropertyName);
            var lookup = bindingInfo.GetLookupControl(this);
            if (lookup != null)
            {
                // Parent.EmployeeID --> LookupControl.SelectedValue
                parentForm.PropertyToControlMap.Add(parentPkPropertyName, lookup.SelectedValueControl.ClientID);
                // Parent.DefaultTextProperty --> LookupControl.Text
                parentForm.PropertyToControlMap.Add(parentDefaultPropertyName, lookup.TextControl.ClientID);
            }

            var ddlOrRadio = bindingInfo.GetListControl(this);
            if (ddlOrRadio != null)
            {
                // Parent.EmployeeID --> BoundControl.SelectedValue
                parentForm.PropertyToControlMap.Add(parentPkPropertyName, ((Control)ddlOrRadio).ClientID);
            }

            FireNewParent(parentForm);
        }

        #endregion

        #region Children.

        /// <summary>Initializes collection editors and sets labels/captions for tab pages.</summary>
        protected virtual void SetupChildrenCollectionControls()
        {
            if (this.ChildrenHidden && this.ChildrenTabContainer != null)
                this.ChildrenTabContainer.Visible = false;

            foreach (IChildrenBinding childrenBinding in this.Bindings.ChildrenBindings)
            {
                var collectionEditor = childrenBinding.GetCollectionEditor(this);
                collectionEditor.Collection = (IList)GetChildrenAdapter(childrenBinding.ChildrenPropertyName).GetChildren(this.BizObject);
                if (string.IsNullOrEmpty(collectionEditor.Caption))
                    collectionEditor.Caption = this.BizObject.GetFieldCaption(childrenBinding.ChildrenPropertyName);

                if (this.ChildrenTabContainer != null)
                {
                    var tabPanel = this.ChildrenTabContainer.Tabs[childrenBinding.TabIndex];
                    if (string.IsNullOrEmpty(tabPanel.HeaderText))
                        tabPanel.HeaderText = collectionEditor.Caption;
                }
            }
        }

        /// <summary>Gets adapter for specified children collection property.</summary>
        protected IChildrenAdapter GetChildrenAdapter(string propertyName)
        {
            return ArrayUtil.Find<IChildrenAdapter>(this.BizObject.GetDescriptor().Children, (adapt) => (adapt.PropertyName == propertyName));
        }

        /// <summary>Displays child collection specified in the event's CommandArgument. Fetches the collection if not already fetched.</summary>
        protected virtual void HandleDisplayChildrenCommand(object triggerButton, CommandEventArgs commandArgsWithChildrenPropertyName)
        {
            string childrenPropertyName = Convert.ToString(commandArgsWithChildrenPropertyName.CommandArgument);
            DisplayChildren(childrenPropertyName);
        }

        /// <summary>Ensures that all items in the child collection specified in the event's CommandArgument implement ISelfValidationObject interface.
        /// Called during the initial binding and wheneever a new or changed item is submitted/accepted, ie. during automatic rebinds.</summary>
        protected virtual void HandleConvertCollectionItemsToBusinessObjects(object collectionEditor, CommandEventArgs commandArgsWithChildrenPropertyName)
        {
            string childrenPropertyName = Convert.ToString(commandArgsWithChildrenPropertyName.CommandArgument);
            var adapter = GetChildrenAdapter(childrenPropertyName);
            adapter.EnsureBusinessObjectsInChildCollection(this.BizObject);
            adapter.EnsureAllParentsForChildren(this.BizObject);
        }

        /// <summary>Creates and adds a new child object via <see cref="IChildrenAdapter"/> to the child collection specified in the event's CommandArgument.</summary>
        protected virtual void HandleCreateNewChildCommand(object collectionEditor, CommandEventArgs commandArgsWithChildrenPropertyName)
        {
            string childrenPropertyName = Convert.ToString(commandArgsWithChildrenPropertyName.CommandArgument);
            IDataObject newChild = GetChildrenAdapter(childrenPropertyName).NewChild(this.BizObject, GetActor());
            ((ICollectionEditor)collectionEditor).AddItems(newChild);
        }

        /// <summary>Saves the child collection specified in the event's CommandArgument to the database if the feature implemented in master business object. Function is enabled in ASCX file by setting the SaveCollectionVisible attribute to "true"</summary>
        protected virtual void HandleSaveChildrenCommand(object collectionEditor, CommandEventArgs commandArgsWithChildrenPropertyName)
        {
            string childrenPropertyName = Convert.ToString(commandArgsWithChildrenPropertyName.CommandArgument);
            SaveChildren(childrenPropertyName);
        }

        /// <summary>Refetches the child collection specified in the event's CommandArgument from the database. Function is enabled in ASCX file by setting the RefetchCollectionVisible attribute to "true"</summary>
        protected virtual void HandleRefetchChildrenCommand(object collectionEditor, CommandEventArgs commandArgsWithChildrenPropertyName)
        {
            string childrenPropertyName = Convert.ToString(commandArgsWithChildrenPropertyName.CommandArgument);
            RefetchChildren(childrenPropertyName);
        }

        /// <summary>Displays item details in the dedicated form. Calls <see cref="OpenSelectedChildDetails"/> method.</summary>
        protected virtual void HandleOpenChildDetailsCommand(object collectionEditor, CommandEventArgs commandArgsWithChildrenPropertyName)
        {
            string childrenPropertyName = Convert.ToString(commandArgsWithChildrenPropertyName.CommandArgument);
            OpenSelectedChildDetails(childrenPropertyName);
        }

        /// <summary>If the handler is attached to the control in ASCX file then a dedicated form is opened to create a new child item. Function is enabled in ASCX file by setting the OnBeginInserting attribute to "HandleBeginInsertingChildCommand".</summary>
        protected virtual void HandleBeginInsertingChildCommand(object collectionEditor, CancelCommandEventArgs beginInsertingEventArgs)
        {
            string childrenPropertyName = Convert.ToString(beginInsertingEventArgs.CommandArgument);
            CreateChildUsingCustomForm(childrenPropertyName, beginInsertingEventArgs);
        }

        /// <summary>If the handler is attached to the control in ASCX file then a dedicated form is opened to edit an existing child item. Function is enabled in ASCX file by setting the OnBeginInserting attribute to "HandleBeginInsertingChildCommand".</summary>
        protected virtual void HandleBeginEditingChildCommand(object collectionEditor, CancelCommandEventArgs beginEditingEventArgs)
        {
            string childrenPropertyName = Convert.ToString(beginEditingEventArgs.CommandArgument);
            EditSelectedChildUsingCustomForm(childrenPropertyName, beginEditingEventArgs);
        }

        /// <summary>Display the specified child collection. Fetches the collection from the data-source if not already fetched.</summary>
        private void DisplayChildren(string childrenPropertyName)
        {
            var bindingInfo = ArrayUtil.Find<IChildrenBinding>(this.Bindings.ChildrenBindings, (info) => (info.ChildrenPropertyName == childrenPropertyName));
            if (bindingInfo == null)
                throw new InvalidOperationException("No binding defined for child collection " + childrenPropertyName);

            bindingInfo.GetCollectionEditor(this).Visible = true;
            var adapter = GetChildrenAdapter(childrenPropertyName);
            bool wasEmpty = adapter.GetChildren(this.BizObject).Count == 0;
            adapter.EnsureChildren(this.BizObject);

            // Must set readonly flag after first fetch to ensure that collection editors start with read-only items.
            bool isNotEmptyAnyMore = adapter.GetChildren(this.BizObject).Count > 0;
            bool isFirstFetch = wasEmpty && isNotEmptyAnyMore;
            if (isFirstFetch)
            {
                foreach (IDataObject childEntity in adapter.GetChildren(this.BizObject))
                    childEntity.IsReadOnly = true;
            }

            bindingInfo.GetCollectionEditor(this).Collection = (IList)adapter.GetChildren(this.BizObject);
            bindingInfo.GetCollectionEditor(this).Refresh();
        }

        private void OpenSelectedChildDetails(string childrenPropertyName)
        {
            // Opens selected child entity in a dedicated form.
            var bindingInfo = ArrayUtil.Find<IChildrenBinding>(this.Bindings.ChildrenBindings, (info) => (info.ChildrenPropertyName == childrenPropertyName));
            if (bindingInfo == null)
                throw new InvalidOperationException("No binding defined for child collection " + childrenPropertyName);

            ICollectionEditor collectionEditor = bindingInfo.GetCollectionEditor(this);
            string[] keys = collectionEditor.SelectedValues;
            if (keys.Length > 0)
            {
                WebFormContext childForm = new WebFormContext(bindingInfo.ChildFormUrl, keys[0]);
                childForm.ReadOnly = true;
                childForm.Mode = OpenFormMode.Popup;
                FireOpenChild(childForm);
            }
        }

        private void CreateChildUsingCustomForm(string childrenPropertyName, CancelEventArgs beginInsertingEventArgs)
        {
            // If a handler is wired ni ASCX file then a dedicated form is opened to create a new child entity.
            var bindingInfo = ArrayUtil.Find<IChildrenBinding>(this.Bindings.ChildrenBindings, (info) => (info.ChildrenPropertyName == childrenPropertyName));
            if (bindingInfo == null)
                throw new InvalidOperationException("No binding defined for child collection: " + childrenPropertyName);

            var adapter = GetChildrenAdapter(childrenPropertyName);
            if (adapter == null)
                throw new InvalidOperationException("No adapter defined for child collection: " + childrenPropertyName);

            if (!this.BizObject.IsNew)
            {
                // Cancel internal CollectionEditor logic and prepare child form context/data.
                beginInsertingEventArgs.Cancel = true;
                WebFormContext childForm = new WebFormContext(bindingInfo.ChildFormUrl);
                string childForeignKeyPropertyName = adapter.GetForeignKey().ChildForeignKey[0].PropertyName;
                childForm.Parameters.Add(childForeignKeyPropertyName, Convert.ToString(this.BizObject.GetPrimaryKeyValue()[0]));
                FireNewChild(childForm);
            }
        }

        private void EditSelectedChildUsingCustomForm(string childrenPropertyName, CancelEventArgs beginEditingEventArgs)
        {
            // If a handler is wired ni ASCX file then a dedicated form is opened to edit an existing child entity.
            var bindingInfo = ArrayUtil.Find<IChildrenBinding>(this.Bindings.ChildrenBindings, (info) => (info.ChildrenPropertyName == childrenPropertyName));
            if (bindingInfo == null)
                throw new InvalidOperationException("No binding defined for child collection " + childrenPropertyName);

            ICollectionEditor collectionEditor = bindingInfo.GetCollectionEditor(this);
            string[] keys = collectionEditor.SelectedValues;
            if (!this.BizObject.IsNew && (keys.Length > 0))
            {
                // Cancel internal CollectionEditor logic and prepare child form context/data.
                beginEditingEventArgs.Cancel = true;
                WebFormContext childForm = new WebFormContext(bindingInfo.ChildFormUrl, keys[0]);
                FireOpenChild(childForm);
            }
        }

        private void SaveChildren(string childrenPropertyName)
        {
            // Saves the child collection if the feature is implemented in master business object.
            var adapter = GetChildrenAdapter(childrenPropertyName);
            BusinessRuleCollection errors = adapter.ValidateChildren(this.BizObject, ValidationLevel.AllBusinessRules);
            if (errors.AllRulesAreObeyed)
                adapter.SaveChildren(this.BizObject, GetActor(), true);
            else
                this.Bindings.FieldBindings.DisplayErrors(errors);
        }

        private void RefetchChildren(string childrenPropertyName)
        {
            GetChildrenAdapter(childrenPropertyName).FetchChildren(this.BizObject);
            DisplayChildren(childrenPropertyName);
        }

        #endregion

        #region Utility.

        /// <summary>Gets current user. Prefer using this method in derived controls instead of UserSession.Actor property.</summary>
        protected virtual IActor GetActor()
        {
            return UserSession.Actor;
        }

        /// <summary>Makes a read-only collection editor.</summary>
        private static void MakeReadOnlyChildren(ICollectionEditor childrenEditor)
        {
            childrenEditor.ReadOnly = true;
            childrenEditor.HideAllButtons();
            childrenEditor.AlwaysEditBoundFields = false;
        }

        /// <summary>Gets control bound to the specified field.</summary>
        private Control GetControlBoundToField(string propertyName)
        {
            var binding = ArrayUtil.Find<IFieldBinding>(this.Bindings.FieldBindings.FieldBindings, (bind) => (bind.Field.BindingName == propertyName));
            return (binding != null) ? (Control)binding.BoundControl : null;
        }

        #endregion
    }
}
